﻿/* 使用方法
public List<LevelData> LevelList;
public LevelItem[] levelItems;

//初始化函数
protected void OnInitialize()
{
    //数据列表
    LevelList = ConfigManager.Instance.LevelData.GetStageList();

    //使用方法1
    //UIDynamic_Content.UpdateItem = UpdateLevelItem;

    //使用方法2 (在一定程度上减少GetComponent带来的gc)
    UIDynamic_Content.UpdateItem2 = UpdateLevelItem2;
    levelItems = new LevelItem[LevelList.Count];

    UIDynamic_Content.Init(LevelList.Count);
}

//使用方法1
private void UpdateLevelItem(GameObject item,int index)
{
    item.GetComponent<LevelItem>().InitItem(LevelList[index]);
}

//使用方法2
private void UpdateLevelItem2(GameObject item, int index,int itemIndex)
{
    if (levelItems[itemIndex] == null)
    {
        levelItems[itemIndex] = item.GetComponent<LevelItem>();
    }
    levelItems[itemIndex].InitItem(LevelList[index]);
}
*/

using UnityEngine;
using System.Collections.Generic;
using UnityEngine.Events;
using UnityEngine.Assertions;

namespace UnityEngine.UI
{
    /// <summary>
    /// 循环动态容器
    /// </summary>
    [DisallowMultipleComponent]
    public class UIDynamic : MonoBehaviour
    {
        /// <summary>
        /// 刷新回调
        /// </summary>
        public UnityAction<GameObject, int> UpdateItem;
        public UnityAction<GameObject, int, int> UpdateItem2;
        /// <summary>
        /// 回收回调
        /// </summary>
        public UnityAction<GameObject, int> CollectItem;
        public enum Arrangement
        {
            Horizontal,
            Vertical,
        }
        [Header("将item复制N个放在content节点下，脚本context菜单提供排列预览")]
        public RectOffset padding;
        public Vector2 cellSize = new Vector2(100,100);
        public Vector2 spacing;
        [Header("滑动方向")]
        public Arrangement scrollAxis = Arrangement.Horizontal;
        [Header("每行个数")]
        public int constraintCount = 1;
        [Header("可见行数")]
        public int lineCountInView = 5;
        /// <summary>
        /// 拓张Size
        /// </summary>
        private Vector2 appendSize;
        private Vector2 offsetPos;
        public ScrollRect scrollRect;
        /// <summary>
        /// 子物体挂载点
        /// </summary>
        public RectTransform content;
        /// <summary>
        /// content组件可移动范围的最大值
        /// </summary>
        public float MaxAnchorPos { get; private set; }
        /// <summary>
        /// 预制体
        /// </summary>
        public GameObject goItemPrefab;
        /// <summary>
        /// Item渐变出现
        /// </summary>
        public bool ItemFadeIn;
        /// <summary>
        /// 数据数量
        /// </summary>
        private int dataCount;
        private int curScrollPerLineIndex = -1;
        /// <summary>
        /// 当前使用列表
        /// </summary>
        private List<UIDynamicItem> listItem = new List<UIDynamicItem>();
        /// <summary>
        /// 回收列表
        /// </summary>
        private Stack<UIDynamicItem> unUseItem = new Stack<UIDynamicItem>();
        /// <summary>
        /// The scroll rect trans.
        /// </summary>
        private RectTransform _scrollRectTrans;
        //用于分配的 item index
        private int _itemIndex = 0;
        private UIDynamicAnim _dynamicAnim;

        private void Start()
        {
            Assert.IsNotNull(scrollRect);
            Assert.IsNotNull(content);
            Assert.IsNotNull(goItemPrefab);

            scrollRect.onValueChanged.AddListener(OnValueChanged);
            //如果是这样的预制体，直接丢到看不见的位置去
            if (goItemPrefab != null && goItemPrefab.transform.IsChildOf(this.transform))
                this.goItemPrefab.transform.position = GetLocalPositionByIndex(-99);

            offsetPos = new Vector2(padding.left, padding.top);
            appendSize = new Vector2(padding.horizontal, padding.vertical);
        }

        public List<UIDynamicItem> useList
        {
            get
            {
                return listItem;
            }
        }

        public RectTransform scrollRectTrans
        {
            get
            {
                if (_scrollRectTrans == null)
                {
                    _scrollRectTrans = scrollRect.GetComponent<RectTransform>();
                }

                return _scrollRectTrans;
            }
        }

        /// <summary>
        /// 用于临时判定item是不是新增的
        /// </summary>
        private int lastDataCount;

        /// <summary>
        /// 初始化
        /// </summary>
        /// <param name="dataCount">Data count.</param>
        public void Init(int dataCount)
        {
            lastDataCount = this.dataCount;
            //第一次进来
            if (curScrollPerLineIndex == -1)
            {
                this.dataCount = dataCount;
                SetUpdateContentSize();
                SetUpdateRectItem(0);
            }
            //数据数量减少
            else if (dataCount < this.dataCount)
            {
                this.dataCount = dataCount;
                SetUpdateContentSize();
                SetUpdateRectItem(GetCurScrollPerLineIndex(), true);
            }
            //数据数量增加
            else
            {
                this.dataCount = dataCount;
                SetUpdateContentSize();
                SetUpdateRectItem(curScrollPerLineIndex, true);
            }
            lastDataCount = this.dataCount;

        }

        private UIDynamicAnim GetDynamicAnim()
        {
            if (_dynamicAnim == null)
            {
                _dynamicAnim = GetComponent<UIDynamicAnim>();
                if (_dynamicAnim == null)
                {
                    _dynamicAnim = gameObject.AddComponent<UIDynamicAnim>();
                    _dynamicAnim.Init();
                }
            }
            return _dynamicAnim;
        }

        /// <summary>
        /// 跳到指定位置
        /// </summary>
        /// <param name="index"></param>
        public void JumpToIndex(int index)
        {

            float value = 0;
            index = index / constraintCount;
            if (scrollAxis == Arrangement.Horizontal)
            {
                value = scrollRectTrans.sizeDelta.x / 2 - (index + 0.5f) * cellSize.x;
                value = Mathf.Clamp01(value);
                scrollRect.horizontalNormalizedPosition = value;
            }
            else
            {
                value = (index + 0.5f) * cellSize.y - scrollRectTrans.sizeDelta.y / 2;
                value = Mathf.Clamp01(value / MaxAnchorPos);
                scrollRect.verticalNormalizedPosition = 1 - value;
            }
        }
        /// <summary>
        /// 让指定下标可见
        /// </summary>
        /// <param name="dataIndex">数据下标</param>
        /// <param name="duration">动画时间</param>
        public void SetIndexInSightSmoothly(int dataIndex, float duration, System.Action<GameObject> callback = null)
        {

            GetDynamicAnim().SetIndexInSightSmoothly(dataIndex, duration, () =>
            {
                if (callback != null)
                {
                    UIDynamicItem tmp;
                    if (IsExistDataByDataIndex(dataIndex, out tmp))
                    {
                        callback(tmp.gameObject);
                    }
                }
            });

        }

        /// <summary>
        /// 删除数据表现
        /// </summary>
        /// <param name="startDataIndex">包含</param>
        public void DeleteLineSmoothly(int startDataIndex,int count)
        {
            float duration = 0.3f;
            int end = startDataIndex + count;
            for (int i = listItem.Count -1; i >= 0 ; i--)
            {
                var item = listItem[i];
                GetDynamicAnim().KillTweener(item);
                if (item.DataIndex >= startDataIndex && item.DataIndex < end)
                {
                    CollectOne(item);
                }
                else if (item.DataIndex >= end)
                {
                    item.DataIndex -= count;
                    GetDynamicAnim().TweenItemPos(item.transform, GetLocalPositionByIndex(item.DataIndex), duration);
                    //item.transform.localPosition = GetLocalPositionByIndex(item.DataIndex);
                }
            }
            this.dataCount -= count;
            GetDynamicAnim().DisableVertival();
            CancelInvoke("ReduceContentSizeSmoothly");
            Invoke("ReduceContentSizeSmoothly", duration);
        }

        /// <summary>
        /// 设置Content的大小
        /// </summary>
        private void ReduceContentSizeSmoothly()
        {
            SetUpdateContentSize(true,()=> 
            {
                CancelInvoke("RefreshGrid");
                Invoke("RefreshGrid", 2);
            });
        }

        private void RefreshGrid()
        {
            Init(this.dataCount);
        }
        
        /// <summary>
        /// 滚动
        /// </summary>
        /// <param name="vt2">Vt2.</param>
        private void OnValueChanged(Vector2 vt2)
        {
            int tmpLineIndex = GetCurScrollPerLineIndex();
            if (tmpLineIndex == curScrollPerLineIndex)
            {
                return;
            }
            SetUpdateRectItem(tmpLineIndex);
        }

        /// <summary>
        /// 刷新区域内的ITEM
        /// </summary>
        /// <param name="scrollPerLineIndex">Scroll per line index.</param>
        /// <param name="forceUpdate">If set to <c>true</c> force update.</param>
        private void SetUpdateRectItem(int scrollPerLineIndex, bool forceUpdate = false)
        {
            curScrollPerLineIndex = scrollPerLineIndex;
            int startDataIndex = curScrollPerLineIndex * constraintCount;
            int endDataIndex = (curScrollPerLineIndex + lineCountInView) * constraintCount;
            endDataIndex = Mathf.Min(endDataIndex, dataCount);
            for (int i = listItem.Count - 1; i >= 0; i--)
            {
                UIDynamicItem item = listItem[i];
                int index = item.DataIndex;
                if (index < startDataIndex || index >= endDataIndex)
                {
                    CollectOne(item);
                }
            }
            //显示
            UIDynamicItem tmpItem = null;
            for (int dataIndex = startDataIndex; dataIndex < endDataIndex; dataIndex++)
            {
                if (dataIndex >= dataCount)
                {
                    continue;
                }
                if (IsExistDataByDataIndex(dataIndex, out tmpItem))
                {
                    if (forceUpdate)
                    {
                        tmpItem.transform.localPosition = GetLocalPositionByIndex(dataIndex);
                        CallUpdateItem(tmpItem);
                    }
                    continue;
                }
                CreateItem(dataIndex);
            }
        }

        /// <summary>
        /// 回收一个
        /// </summary>
        private void CollectOne(UIDynamicItem item)
        {
            item.DataIndex = -1;
            item.name = "collectItem";
            listItem.Remove(item);
            unUseItem.Push(item);
            item.transform.localPosition = CollectPosition;
            item.gameObject.SetActive(false);
            CallCollectItem(item);
        }

        private Vector3 CollectPosition
        {
            get
            {
                return new Vector3(-cellSize.x*2 - spacing.x*2, cellSize.y*2 + spacing.y*2);
            }
        }


        /// <summary>
        /// 获取当前index下对应Content下的本地坐标
        /// </summary>
        /// <returns>The local position by index.</returns>
        /// <param name="dataIndex">Index.</param>
        private Vector2 GetLocalPositionByIndex(int dataIndex)
        {
            float x = 0f;
            float y = 0f;
            switch (scrollAxis)
            {
                case Arrangement.Horizontal: //水平方向
                    x = (dataIndex / constraintCount) * (cellSize.x + spacing.x);
                    y = -(dataIndex % constraintCount) * (cellSize.y + spacing.y);
                    break;
                case Arrangement.Vertical://垂着方向
                    x = (dataIndex % constraintCount) * (cellSize.x + spacing.x);
                    y = -(dataIndex / constraintCount) * (cellSize.y + spacing.y);
                    break;
            }
            return new Vector2(x + offsetPos.x, y - offsetPos.y);
        }
        /// <summary>
        /// 创建元素
        /// </summary>
        /// <param name="dataIndex">Data index.</param>
        private void CreateItem(int dataIndex)
        {
            UIDynamicItem item;
            if (unUseItem.Count > 0)
            {
                item = unUseItem.Pop();
            }
            else
            {
                item = AddChild(goItemPrefab, content).AddComponent<UIDynamicItem>();
                item.ItemIndex = _itemIndex++;
            }
            if (ItemFadeIn && dataIndex >= lastDataCount)
                GetDynamicAnim().FadeIn(item);
            item.DataIndex = dataIndex;
            item.transform.localPosition = GetLocalPositionByIndex(item.DataIndex);
            item.name = dataIndex.ToString("D5");
            item.gameObject.SetActive(true);
            listItem.Add(item);
            CallUpdateItem(item);
        }

        /// <summary>
        /// 刷新回调
        /// </summary>
        /// <param name="item">Item.</param>
        private void CallUpdateItem(UIDynamicItem item)
        {
            if (UpdateItem != null)
                UpdateItem(item.gameObject, item.DataIndex);

            if (UpdateItem2 != null)
                UpdateItem2(item.gameObject, item.DataIndex, item.ItemIndex);
        }


        /// <summary>
        /// 回收回调
        /// </summary>
        /// <param name="item">Item.</param>
        private void CallCollectItem(UIDynamicItem item)
        {
            if (CollectItem != null)
                CollectItem(item.gameObject, item.DataIndex);
        }

        /// <summary>
        /// 当前数据是否存在List中
        /// </summary>
        /// <returns><c>true</c> if this instance is exist data by data index the specified dataIndex item; otherwise, <c>false</c>.</returns>
        /// <param name="dataIndex">Data index.</param>
        /// <param name="item">Item.</param>
        private bool IsExistDataByDataIndex(int dataIndex, out UIDynamicItem item)
        {
            item = null;
            if (listItem == null || listItem.Count <= 0)
            {
                return false;
            }
            for (int i = 0; i < listItem.Count; i++)
            {
                if (listItem[i].DataIndex == dataIndex)
                {
                    item = listItem[i];
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// 根据Content偏移,计算当前开始显示所在数据列表中的行或列
        /// </summary>
        /// <returns>The current scroll per line index.</returns>
        public int GetCurScrollPerLineIndex()
        {
            int index = 0;
            switch (scrollAxis)
            {
                case Arrangement.Horizontal: //水平方向
                    index = Mathf.FloorToInt(Mathf.Abs(Mathf.Clamp(content.anchoredPosition.x, MaxAnchorPos, 0)) / (cellSize.x + spacing.x));
                    break;
                case Arrangement.Vertical://垂着方向
                    index = Mathf.FloorToInt(Mathf.Clamp(content.anchoredPosition.y, 0, MaxAnchorPos) / (cellSize.y + spacing.y));
                    break;
            }
            return index;
        }


        /// <summary>
        /// 设置Content的大小
        /// </summary>
        private void SetUpdateContentSize(bool smoothly = false,System.Action callback = null)
        {
            int lineCount = Mathf.CeilToInt((float)dataCount / constraintCount);
            switch (scrollAxis)
            {
                case Arrangement.Horizontal:
                    {

                        Vector2 before = content.sizeDelta;
                        content.sizeDelta = new Vector2(cellSize.x * lineCount + spacing.x * Mathf.Max((lineCount - 1), 0), content.sizeDelta.y);
                        content.sizeDelta = content.sizeDelta + appendSize;
                        float maxX = Mathf.Max(content.sizeDelta.x - scrollRectTrans.sizeDelta.x, 0);
                        //content变小，要调整位置，以防止被吊起
                        if (content.sizeDelta.x < before.x && content.anchoredPosition.x < maxX)
                        {
                            content.anchoredPosition = new Vector2(maxX, content.anchoredPosition.y);
                        }
                        MaxAnchorPos = Mathf.Min(0, scrollRectTrans.sizeDelta.x - content.sizeDelta.x);
                    }
                    break;
                case Arrangement.Vertical:
                    {
                        Vector2 before = content.sizeDelta;
                        Vector2 newSize = new Vector2(content.sizeDelta.x, cellSize.y * lineCount + spacing.y * Mathf.Max((lineCount - 1), 0));
                        newSize+= appendSize;
                        float maxY = Mathf.Max(newSize.y - scrollRectTrans.sizeDelta.y, 0);
                        //content变小，要调整位置，以防止被吊起
                        if (newSize.y < before.y && content.anchoredPosition.y > maxY)
                        {
                            Vector2 newpos = new Vector2(content.anchoredPosition.x, maxY);
                            if (smoothly)
                            {
                                GetDynamicAnim().DisableVertival();
                                content.sizeDelta = newSize;
                                GetDynamicAnim().DoAnchorPos(content, newpos,0.3f, callback);
                            }
                            else
                            {
                                content.sizeDelta = newSize;
                                content.anchoredPosition = newpos;
                                callback?.Invoke();
                            }
                        }
                        else
                        {
                            content.sizeDelta = newSize;
                            callback?.Invoke();
                        }
                        MaxAnchorPos = Mathf.Max(0, newSize.y - scrollRectTrans.sizeDelta.y);

                    }
                    break;
            }
        }


        /// <summary>
        /// 实例化预制对象
        /// </summary>
        /// <returns>The child.</returns>
        /// <param name="goPrefab">Go prefab.</param>
        /// <param name="parent">Parent.</param>
        private GameObject AddChild(GameObject goPrefab, Transform parent)
        {
            if (goPrefab == null || parent == null)
            {
                Debug.LogError((object)"异常。UIWarpContent.cs addChild(goPrefab = null  || parent = null)");
                return null;
            }
            GameObject goChild = GameObject.Instantiate(goPrefab) as GameObject;
            goChild.layer = parent.gameObject.layer;
            goChild.transform.SetParent(parent, false);
            //预制体轴为居中制作，这里按左上角排版
            var tmp = goChild.transform as RectTransform;
            tmp.anchorMin = tmp.anchorMax = new Vector2(0, 1);
            tmp.pivot = new Vector2(0, 1);
            return goChild;
        }

        void OnDestroy()
        {

            scrollRect = null;
            content = null;
            goItemPrefab = null;
            UpdateItem = null;

            listItem.Clear();
            unUseItem.Clear();

            listItem = null;
            unUseItem = null;
        }

        private void Reset()
        {
            RectTransform rect = GetComponent<RectTransform>();
            rect.anchorMax = Vector2.up;
            rect.anchorMin = Vector2.up;
            rect.pivot = Vector2.up;
            rect.anchoredPosition = Vector2.zero;
            scrollRect = GetComponentInParent<ScrollRect>();
            content = rect;
        }

        [ContextMenu("Content子物体排列预览")]
        void SortChild()
        {
            content.anchorMax = Vector2.up;
            content.anchorMin = Vector2.up;
            content.pivot = Vector2.up;
            content.anchoredPosition = Vector2.zero;
            offsetPos = new Vector2(padding.left, padding.top);
            appendSize = new Vector2(padding.horizontal, padding.vertical);

            dataCount = transform.childCount;
            SetUpdateContentSize();
            for (int i = 0; i < transform.childCount; i++)
            {
                var tmp = transform.GetChild(i) as RectTransform;
                tmp.anchorMin = tmp.anchorMax = new Vector2(0, 1);
                tmp.pivot = new Vector2(0, 1);
                tmp.anchoredPosition = GetLocalPositionByIndex(i);
            }
        }
    }

}